/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.commonmodel.core.util;

import com.inspur.edp.cef.api.session.ICefSession;
import com.inspur.edp.commonmodel.core.session.serviceinterface.SessionCacheInfoService;
import com.inspur.edp.commonmodel.core.session.serviceinterface.SessionConfigService;
import com.inspur.edp.commonmodel.core.session.tableentity.DBConnectInfo;
import com.inspur.edp.commonmodel.core.session.tableentity.SessionCacheInfo;
import com.inspur.edp.commonmodel.core.session.tableentity.SessionConfig;
import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.commons.transaction.JpaTransaction;
import io.iec.edp.caf.commons.transaction.TransactionPropagation;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.data.orm.jpa.DynamicTableContext;
import io.iec.edp.caf.databaseobject.api.entity.DataType;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectColumn;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectTable;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectType;
import io.iec.edp.caf.databaseobject.api.service.IDatabaseObjectDeployService;
import io.iec.edp.caf.databaseobject.api.service.IDatabaseObjectRtService;
import io.iec.edp.caf.lock.service.api.api.DistributedLock;
import io.iec.edp.caf.lock.service.api.api.DistributedLockFactory;
import io.iec.edp.caf.tenancy.api.context.MultiTenantContextInfo;
import io.iec.edp.caf.tenancy.api.entity.DbConnectionInfo;
import io.iec.edp.caf.tenancy.core.context.MultiTenantContextHolder;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.SneakyThrows;

public class SessionIncByDbUtil implements SessionIncInterface {

  private SessionConfig config;

  public SessionIncByDbUtil(SessionConfig config, boolean isCreateTable) {
    this.config = config;
    if (isCreateTable) {
      createTable();
    }
  }

  @SneakyThrows
  private void createTable() {
    if (config.isSessionTableExist()) {
      return;
    }
    DistributedLock lock= null;
    try {
      lock = SpringBeanUtils.getBean(DistributedLockFactory.class).createLock(
          "SessionIncByDbUtil", Duration.ofSeconds(15L));
      if(lock.isAcquired()) {
        IDatabaseObjectRtService dboRtService = SpringBeanUtils
            .getBean(IDatabaseObjectRtService.class);
        IDatabaseObjectDeployService dboDeployService = SpringBeanUtils
            .getBean(IDatabaseObjectDeployService.class);
        String tableName = config.getConnectInfo().getDBConnectInfo().getTableName();
        if (config.getConnectInfo().getDBConnectInfo().getTableCount() <= 1) {
          if (!isTableExist(tableName, dboRtService)) {
            buildTable(0, dboDeployService);
          }
        } else {
          for (int i = 1; i <= config.getConnectInfo().getDBConnectInfo().getTableCount();
              i++) {
            if (!isTableExist(tableName + i, dboRtService)) {
              buildTable(i, dboDeployService);
            }
          }
        }
        updateConfig();
      } else {
        throw new RuntimeException("create table by dbo time out");
      }
    }finally {
      if(lock != null)
        lock.close();
    }
  }

  @Override
  public List<SessionCacheInfo> getSessionCacheInfos(String sessionId, int version) {
    switchTable();
    SessionCacheInfoService service = SpringBeanUtils.getBean(SessionCacheInfoService.class);
    return service.getSessionCacheInfos(sessionId, version);
  }

  @Override
  public void saveSessionCache(SessionCacheInfo info, ICefSession items) {
    switchTable();
    SessionCacheInfoService service = SpringBeanUtils.getBean(SessionCacheInfoService.class);
    if (info.getSessionChangeSet() != null && info.getSessionChangeSet().isReset()) {
      service.deleteSessionCacheInfos(info.getSessionId(), info.getVersion());
    }
    service.saveSessionCacheInfo(info, items.getSessionItems());
  }

  @Override
  public void clearSessionCache(String sessionId) {
    switchTable();
    SessionCacheInfoService service = SpringBeanUtils.getBean(SessionCacheInfoService.class);
    service.clearSessionCacheInfos(sessionId);
  }

  @Override
  public boolean isLatestVersion(String id, int ver) {
    switchTable();
    SessionCacheInfoService service = SpringBeanUtils.getBean(SessionCacheInfoService.class);
    return service.isLatestVersion(id, ver);
  }

  @Override
  public List<List<SessionCacheInfo>> traversalSessionCacheInfos(List<Date> dates) {
    List<List<SessionCacheInfo>> sessionCacheInfos = new ArrayList<>();
    SessionCacheInfoService service = SpringBeanUtils.getBean(SessionCacheInfoService.class);
    if (config.getConnectInfo().getDBConnectInfo().getTableCount() <= 1) {
      changeTable(config.getConnectInfo().getDBConnectInfo().getTableName());
      List<SessionCacheInfo> temp = service.traversalSessionCacheInfos(dates.get(0));
      sessionCacheInfos.add(temp);
      return sessionCacheInfos;
    }
    for (int i = 1; i <= dates.size(); i++) {
      changeTable(config.getConnectInfo().getDBConnectInfo().getTableName() + i);
      List<SessionCacheInfo> temp = service.traversalSessionCacheInfos(dates.get(i - 1));
      sessionCacheInfos.add(temp);
    }
    return sessionCacheInfos;
  }

  @Override
  public void batchClear(List<List<String>> sessionIds) {
    SessionCacheInfoService service = SpringBeanUtils.getBean(SessionCacheInfoService.class);
    if (config.getConnectInfo().getDBConnectInfo().getTableCount() <= 1) {
      if (sessionIds.get(0) == null || sessionIds.get(0).isEmpty()) {
        return;
      }
      changeTable(config.getConnectInfo().getDBConnectInfo().getTableName());
      service.batchClear(sessionIds.get(0));
    } else {
      for (int i = 1; i <= sessionIds.size(); i++) {
        if (sessionIds.get(i - 1) == null || sessionIds.get(i - 1).isEmpty()) {
          continue;
        }
        changeTable(config.getConnectInfo().getDBConnectInfo().getTableName() + i);
        service.batchClear(sessionIds.get(i - 1));
      }
    }
  }

  private boolean isTableExist(String tableCode, IDatabaseObjectRtService dboRtService) {
    return dboRtService.getDatabaseObjectByCode(tableCode) != null;
  }

  private void buildTable(int i, IDatabaseObjectDeployService dboDeployService) {
    DatabaseObjectTable dbo = new DatabaseObjectTable();
    String tableName;
    if (i == 0) {
      tableName = config.getConnectInfo().getDBConnectInfo().getTableName();
    } else {
      tableName = config.getConnectInfo().getDBConnectInfo().getTableName() + i;
    }
    dbo.setCode(tableName);
    dbo.setName(tableName);
    dbo.setColumns(createColumns());
    dbo.setVersion(UUID.randomUUID().toString());
    dboDeployService.deployDatabaseObject(dbo, null);
  }

  private void updateConfig() {
    SessionConfigService service = SpringBeanUtils.getBean(SessionConfigService.class);
    config.setSessionTableExist(true);
    service.updateConfigCache(config);
  }

  private List<DatabaseObjectColumn> createColumns() {

    List<DatabaseObjectColumn> colums = new ArrayList<>();

    DatabaseObjectColumn column1 = new DatabaseObjectColumn();
    column1.setCode("ID");
    column1.setName("ID");
    column1.setType(DatabaseObjectType.Column);
    column1.setTypeStr("Column");
    column1.setLength(100);
    column1.setDefaultValue("");
    column1.setDataTypeStr(DataType.Varchar);
    column1.setDataType(DataType.Varchar);
    column1.setIfPrimaryKey(true);
    column1.setNullable(false);
    column1.setUnique(true);
    colums.add(column1);

    DatabaseObjectColumn column2 = new DatabaseObjectColumn();
    column2.setCode("sessioncontent");
    column2.setName("sessioncontent");
    column2.setType(DatabaseObjectType.Column);
    column2.setTypeStr("Column");
    column2.setDefaultValue("");
    column2.setDataTypeStr(DataType.Clob);
    column2.setDataType(DataType.Clob);
    column2.setIfPrimaryKey(false);
    column2.setNullable(true);
    column2.setUnique(false);
    colums.add(column2);

    DatabaseObjectColumn column3 = new DatabaseObjectColumn();
    column3.setCode("sessionid");
    column3.setName("sessionid");
    column3.setType(DatabaseObjectType.Column);
    column3.setTypeStr("Column");
    column3.setLength(36);
    column3.setDefaultValue("");
    column3.setDataTypeStr(DataType.Varchar);
    column3.setDataType(DataType.Varchar);
    column3.setIfPrimaryKey(false);
    column3.setNullable(true);
    column3.setUnique(false);
    colums.add(column3);

    DatabaseObjectColumn column4 = new DatabaseObjectColumn();
    column4.setCode("version");
    column4.setName("version");
    column4.setType(DatabaseObjectType.Column);
    column4.setTypeStr("Column");
    column4.setLength(6);
    column4.setDefaultValue("");
    column4.setDataTypeStr(DataType.Int);
    column4.setDataType(DataType.Int);
    column4.setIfPrimaryKey(false);
    column4.setNullable(true);
    column4.setUnique(false);
    colums.add(column4);

    DatabaseObjectColumn column5 = new DatabaseObjectColumn();
    column5.setCode("createdon");
    column5.setName("createdon");
    column5.setType(DatabaseObjectType.Column);
    column5.setTypeStr("Column");
    column5.setLength(6);
    column5.setDefaultValue("");
    column5.setDataTypeStr(DataType.TimeStamp);
    column5.setDataType(DataType.TimeStamp);
    column5.setIfPrimaryKey(false);
    column5.setNullable(true);
    column5.setUnique(false);
    colums.add(column5);

    return colums;
  }

  private void switchTable() {
    if (config.getConnectInfo().getDBConnectInfo().getTableCount() > 1) {
      int tableCount =
          (Math.abs(CAFContext.current.getUserId().hashCode())) % config.getConnectInfo()
              .getDBConnectInfo().getTableCount();
      tableCount++;
      changeTable(config.getConnectInfo().getDBConnectInfo().getTableName() + tableCount);
    } else {
      changeTable(config.getConnectInfo().getDBConnectInfo().getTableName());
    }
  }

  private void changeTable(String tableName) {
    Map<String, String> map = new HashMap<>(16);
    map.put("sessioncacheinfo", tableName);
    DynamicTableContext context = DynamicTableContext.createContext(map);
  }


  //暂未实现连接配置的数据库，目前只支持连接su指定库
  private void changeConnectByConfig() {
    SessionConfigService service = SpringBeanUtils.getBean(SessionConfigService.class);
    SessionConfig config = service.getSessionConfig();
    DBConnectInfo dbInfo = config.getConnectInfo().getDBConnectInfo();
    if (dbInfo.isUseDefConnect()) {
      return;
    }
    MultiTenantContextInfo contextInfo = new MultiTenantContextInfo();
    contextInfo.setTenantId(CAFContext.current.getTenantId());
    contextInfo.setUserId(CAFContext.current.getUserId());
    contextInfo.setAppCode(CAFContext.current.getAppCode());
    contextInfo.setServiceUnit(CAFContext.current.getCurrentSU());
    DbConnectionInfo info = new DbConnectionInfo();
    info.setDbType(CAFContext.current.getDbType());
    info.setLastModifiedTime(new Date());
    info.setConnectionString(buildConnectInfo(dbInfo));
    contextInfo.setDbConnectionInfo(info);
    MultiTenantContextHolder.set(contextInfo);
  }

  private String buildConnectInfo(DBConnectInfo info) {
    return "Server=" + info.getServerIp() + ";Port=" + info.getPort() + ";Database=" + info
        .getDbInstance() + ";User Id=" + info.getUserId() + ";Password=" + info.getPassword()
        + ";MaxPoolSize=" + info.getMaxPoolSize() + ";Pooling=True;Enlist=" + info.isEnlist() + "";
  }

  @Override
  public boolean isStorageInited() {
    String tableName = config.getConnectInfo().getDBConnectInfo().getTableName();
    IDatabaseObjectRtService dboRtService = SpringBeanUtils.getBean(IDatabaseObjectRtService.class);
    if (config.getConnectInfo().getDBConnectInfo().getTableCount() <= 1) {
      if (isTableExist(tableName, dboRtService)) {
        return true;
      }
    } else {
      for (int i = 1; i <= config.getConnectInfo().getDBConnectInfo().getTableCount();
          i++) {
        if (isTableExist(tableName + i, dboRtService)) {
          return true;
        }
      }
    }
    return false;
  }
}
